function Vue(opt = {}) {
  // 将所有传入属性挂载在 $opt 上
  this.$opt = opt;
  // 将数据挂载在 $data 上
  let data = this.$data = opt.data;

  // 数据劫持
  observe(data);

  // 添加代理，利用this代理了this.$data(这样我们对数据操作时就不需要用app.$data.name，直接使用app.name)
  for (let key in data) {
    Object.defineProperty(this, key, {
      configurable: true, // 可配置属性
      get() {
        return this.$data[key];     // this.name = lzx
      },
      set(newValue) {
          this.$data[key] = newValue;
      }
    });
  }

  // 初始化computed，并将this指向实例
  initComputed.call(this);

  // 类似于created()(此时组件实例创建完成，属性已绑定，'不过DOM(el)还没生成' => 'DOM已经生成, {{}}语法还未被替换')
  this.name = 'luzhixin';

  // 编译，对模板进行编译，替换{{}}模板
  new compile(opt.el, this);

  // 所有事情处理好后执行mounted钩子函数
  opt.mounted.call(this);
}

// 数据劫持函数
function Observe(data) {

  // 遍历对象，依次给它添加get、set方法
  for(let key in data) {
    let value = data[key];

    // 递归调用，实现深层数据劫持
    observe(value);

    // 添加数据订阅
    let subscriber = new Subscriber();

    // 利用defineProperty来定义属性
    Object.defineProperty(data, key, {
      // value: '',  // 属性对应的值。可以是任何有效的JavaScript值:数值，对象，函数等，默认为: undefined

      configurable: true, // 是否可以配置对象，删除属性，默认：false

      // writable: true, // 是否可以修改对象，默认：false

      // enumerable: true, // 是否可以枚举，默认：false

      get() { // 属性的 getter 函数，如果没有 getter，则为 undefined
        // 将watcher添加到订阅事件中
        Subscriber.target && subscriber.addNode(Subscriber.target);
        return value;
      },

      set(newValue) { // 属性的 setter 函数，如果没有 setter，则为 undefined
        // 如果值没有发生改变则忽略
        if(value === newValue) return;
        
        value = newValue; // 设置最新的值
        observe(newValue);  // 值改变之后，再次进行数据劫持(如果修改的值是一个对象，需要对对象进行深度劫持)
        subscriber.update();  // 让watcher中所有的update方法执行
      },
    });
  }
}

function observe(data) {
  // 如果不是对象的话就直接return, 防止递归溢出
  if (!data || typeof data !== 'object') return;

  return new Observe(data);
}

// 编译模板函数
function Compile(el, vm) {
  
  [].forEach.call(el.childNodes, node => {
    let regTestText = /\{\{(.*?)\}\}/g; // 正则匹配 {{}}

    // 包含 {{}} 语法
    if(/\S/.test(node.nodeValue) && regTestText.test(node.innerText)) {
      // 获取元素内文本内容, 带(样式)空格和换行---(替换后会失去样式, 变成类span平铺)
      // let text = node.textContent;
      // 获取元素内文本, 带(样式)空格---(替换后会失去样式, 变成类div平铺)
      // let text = node.innerText;
      // 获取元素内HTML
      let text = node.innerHTML;

      // // 最近一次正则匹配的值(数组)
      // const keyArr = RegExp.$1.trim().split('.');
      // // 数据处理
      // const _vm = dataProcessing(vm, keyArr);
      
      // // {{}} 中内容替换
      // node.textContent = text.replace(regTestText, _vm).trim();

      // // 添加监听
      // new Watcher(vm, RegExp.$1.trim(), newValue => {
      //   node.textContent = text.replace(regTestText, newValue).trim();
      // });

      function replaceText(hasWatcher) {
        node.innerHTML = text.replace(regTestText, (nodeText, actText) => {
          // nodeText {{ a.b }}
          // actText a.b

          // 添加监听
          new Watcher(vm, actText.trim(), () => replaceText(true));
          // if(!hasWatcher) {
          //   new Watcher(vm, actText.trim(), () => replaceText(true));
          // }
          // key数组
          const keyArr = actText.trim().split('.') || [];
          // 数据处理并返回
          return dataProcessing(vm, keyArr);
        }).trim();
      }

      // 文本替换、监听
      replaceText()

    }else if(node.firstElementChild) { // 存在子节点时
      // 递归
      Compile(node, vm);
    }else if(node.nodeName === 'INPUT') { // 为input输入框时
      // 获取input中dom上的所有属性, 是个类数组
      let attributesArr = node.attributes;
      // 数字输入框需要转成数字类型
      const isNumber = Array.from(attributesArr).some(attr => attr.value === 'number');
      Array.from(attributesArr).forEach(attr => {
        let name = attr.name;     // v-model              type
        let key = attr.value;     // infos.likes[one]     text|number
        if (name === 'v-model'){
          node.value = dataProcessing(vm, key.split('.'));   // 取data中的remarks进行赋值

          // 添加监听
          new Watcher(vm, key, newValue => {
            node.value = newValue;
          });

          node.addEventListener('input', e => {
            const newValue = isNumber ? Number(e.target.value) : e.target.value;
            
            // 获取数组
            const _keyArr = key.trim().split('.');
            const keyArr = dataProcessing([], _keyArr, false);

            // function setValue(obj, keyArray, value) {
            //   // 如果不是对象 | keyArray为空 的话就直接return, 防止递归溢出
            //   if (!obj || typeof obj !== 'object' || !keyArray.length) return;

            //   const _keyArray = [...keyArray];
            //   // 第一个key
            //   const firstKey = _keyArray[0];

            //   if(obj[firstKey] && _keyArray.length !== 1) {
            //     // 删除key数组中使用过的key
            //     _keyArray.shift();
            //     // 递归
            //     setValue(obj[firstKey], _keyArray, value);
            //   }else if(obj[firstKey] && _keyArray.length === 1) {
            //     // 而值的改变会调用set，set中又会调用update(PublishSubscribe中)，之后调用Watcher的update方法实现更新
            //     obj[firstKey] = value;
            //   }
            // }
            // // 方法一：设置值
            // setValue(vm, keyArr, newValue);

            // 方法二：设置值
            return keyArr.reduce((val, key, index) => {
              if (index === keyArr.length - 1) {
                return val[key] = newValue;
              };
              return val[key];
            }, vm);
          });
        }
      })
    };

  })

  
}

function compile(el, vm) {
  // 初始化把el挂载到实例上方便调用
  const fragment = vm.$el = document.querySelector(el);

  new Compile(fragment, vm);
}

// 进行订阅
function Subscriber() {
  this.nodes = [];

  // get发生时->把监听当前数据的Watcher对象进行收集
  this.addNode = function(node) {
    // // 会造成大量重复的Watcher, 导致性能下降(下次更新时会执行所有Watcher)
    // this.nodes.push(node);

    // 判断, 如果有相同regKeys时则不需要再次添加Watcher了
    if(!this.nodes.some(n => n.regKeys === node.regKeys)) this.nodes.push(node);
  }

  // set发生时->执行对应变化数据的监听函数的update(Watcher)事件
  this.update = function() {
    this.nodes.forEach(node => node.update());
  }
}

// 监听函数
function Watcher(vm, regKeys, fn) {
  this.vm = vm;
  this.regKeys = regKeys;
  this.fn = fn;

  // 添加一个事件target标识
  Subscriber.target = this;

  // 正则匹配的值(数组)
  let keyArr = regKeys.split('.');
  // 对数据进行处理
  const _vm = dataProcessing(vm, keyArr); // 获取数据时, 默认就会调用get方法

  // 清空target标识
  Subscriber.target = null;
}

// 当set发生时
Watcher.prototype.update = function() {
  let _vm = this.vm;

  let keyArr = this.regKeys.split('.');
  // 对数据进行处理
  _vm = dataProcessing(_vm, keyArr);
  // 执行文本替换
  this.fn(_vm);
}

// 对 a.b a[0] a.b[0] a[0].b a[0][0] 类似数据进行处理
function dataProcessing(vm, keyArr, isGetValue = true) {
  let _vm = vm;
  
  if(isGetValue) { // 获取实际值
    // keyArr.forEach(k => _vm = _vm[k]);
    keyArr.forEach(k => {
      // // result1：a[0][1] => ['[0]', '[1]'] => ['0', '1']
      // // result2：a => null([]) => []
      // const arr1 = (k.match(/(\[\w+\])/g) || []).map(i => i.replace(/[\[\]]/g, ''));
      // if(arr1.length) {
      //   // 'a[0][1]' => 'a' 
      //   const parentKey = k.replace(/(\[\w+\])/g, '');
      //   _vm = _vm[parentKey];

      //   // 方法1
      //   // arr1.forEach(a => _vm = _vm[a]);
      //   // 方法2
      //   _vm = arr1.reduce((val, key) => val[key], _vm);
      // }else {
      //   _vm = _vm[k];
      // }

      // result：a[0][1] => ['a', '[0]', '[1]'] => ['a', '0', '1']
      // // const parentKey = k.split('[')[0];
      // const parentKey = k.replace(/(\[\w+\])/g, '');
      // const arr = (k.match(/(\[\w+\])/g) || []).map(i => i.replace(/[\[\]]/g, ''));
      // arr.unshift(parentKey);

      const arr = [ k.replace(/(\[\w+\])/g, ''), ...((k.match(/(\[\w+\])/g) || []).map(i => i.replace(/[\[\]]/g, ''))) ];
      _vm =  arr.reduce((val, key) => val[key], _vm);
    });
  }else { // 获取key数组
    _vm = keyArr.map((k, i) => {
      const regTestText = /(\[\w+\])/g;
      // 不存在[...]情况
      if(!regTestText.test(k)) {
        return k;
      }else{ // 有a[0][0]类似 => ['a', '0', '0']
        return [ k.replace(/(\[\w+\])/g, ''), ...((k.match(/(\[\w+\])/g) || []).map(i => i.replace(/[\[\]]/g, ''))) ];
      }
    });
    // 多维数组变成一维
    function arrayProcessing(arr){
      // ['a', ['b', '0'], 'c'] => ['a', 'b', '0', 'c']
      return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? arrayProcessing(cur) : cur), []);
    }
    _vm = arrayProcessing(_vm);
  }

  return _vm;
}

// 初始化computed
function initComputed() {
  let vm = this;
  // 从opt上拿到computed属性，得到的一个(函数)对象
  let computed = this.$opt.computed;

  Object.keys(computed).forEach(key => { // key => 方法名
    Object.defineProperty(vm, key, {
        // 判断key是对象还是函数，函数直接调get方法；对象则手动调一下get方法
        // 这里不需要单独new Watcher去监听变化，因为在调用方法时获取this.scores.language的值时会调用get方法
        get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
    });
  });
}